// TODO: CODE REVIEW & CLEANUP!
//
// TouchScrollViewer.cs
//
// Implements TouchScrollViewer and related types.
//

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using HomeUX.ClientControlSupport;
using HomeUX.UiUtilities;

namespace HomeUX.Controls
{

/// <summary>
/// A touch-friendly control that vertically scrolls another visible element.
/// </summary>
///
[TemplatePart(Name = LayoutRootName, Type = typeof(Panel))]
[TemplatePart(Name = ScrollButtonColumnName, Type = typeof(ColumnDefinition))]
[TemplatePart(Name = ScrollSliderColumnName, Type = typeof(ColumnDefinition))]
[TemplatePart(Name = ScrollViewerFocusControlName, Type = typeof(Control))]
[TemplatePart(Name = ScrollViewerName, Type = typeof(ScrollViewer))]
[TemplatePart(Name = ExtentParentName, Type = typeof(ContentPresenter))]
[TemplatePart(Name = LineUpButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = PageUpButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = PageDownButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = LineDownButtonName, Type = typeof(ButtonBase))]
[TemplatePart(Name = TouchSliderName, Type = typeof(TouchSlider))]
[TemplateVisualState(GroupName = FocusStatesName, Name = UnfocusedName)]
[TemplateVisualState(GroupName = FocusStatesName, Name = FocusedName)]
[TemplateVisualState(GroupName = WaitingStatesName, Name = IdleStateName)]
[TemplateVisualState(GroupName = WaitingStatesName, Name = WaitingStateName)]
public class TouchScrollViewer : ContentControl, IControlHelperConnector
{
	const string LayoutRootName = "LayoutRoot";
    /// <summary>
    /// 
    /// </summary>
    const string ScrollButtonColumnName = "ScrollButtonColumn";
    /// <summary>
    /// TODO
    /// </summary>
    const string ScrollSliderColumnName = "ScrollSliderColumn";
    const string ScrollViewerFocusControlName = "ScrollViewerFocusControl";
	const string ScrollViewerName = "ScrollViewer";
	const string ExtentParentName = "ExtentParent";
	const string LineUpButtonName = "LineUpButton";
	const string PageUpButtonName = "PageUpButton";
	const string PageDownButtonName = "PageDownButton";
	const string LineDownButtonName = "LineDownButton";
	const string TouchSliderName = "TouchSlider";

    const string FocusStatesName = "FocusStates";
    const string UnfocusedName = "Unfocused";
    const string FocusedName = "Focused";

    const string WaitingStatesName = "WaitingStates";
    const string IdleStateName = "Idle";
    const string WaitingStateName = "Waiting";

    public static readonly DependencyProperty ScrollButtonVisibilityProperty =
        DependencyProperty.Register("ScrollButtonVisibility", typeof(UiVisibility),
		typeof(TouchScrollViewer),
		new PropertyMetadata(TouchScrollViewer.OnScrollButtonVisibilityPropertyChanged));

    public static readonly DependencyProperty ScrollSliderVisibilityProperty =
        DependencyProperty.Register("ScrollSliderVisibility", typeof(UiVisibility),
		typeof(TouchScrollViewer),
		new PropertyMetadata(TouchScrollViewer.OnScrollSliderVisibilityPropertyChanged));

    public static readonly DependencyProperty IsWaitingProperty =
        DependencyProperty.Register("IsWaiting", typeof(bool), typeof(TouchScrollViewer),
            new PropertyMetadata(TouchScrollViewer.OnIsWaitingPropertyChanged));

    public static readonly DependencyProperty PageButtonVisibilityProperty =
        DependencyProperty.Register("PageButtonVisibility", typeof(Visibility), typeof(TouchScrollViewer), null);

    public static readonly DependencyProperty ViewportMaxHeightProperty =
        DependencyProperty.Register("ViewportMaxHeight", typeof(double), typeof(TouchScrollViewer), null);
    public static readonly DependencyProperty ViewportMaxWidthProperty =
        DependencyProperty.Register("ViewportMaxWidth", typeof(double), typeof(TouchScrollViewer), null);

    public static readonly DependencyProperty SuppressFocusDisplayProperty =
        DependencyProperty.Register("SuppressFocusDisplay", typeof(bool), typeof(TouchScrollViewer), null);

    /// <summary>
    /// TODO
    /// </summary>
    Panel _layoutRoot;

    /// <summary>
    /// TODO
    /// </summary>
    ColumnDefinition _scrollButtonColumn;

    /// <summary>
    /// TODO
    /// </summary>
    ColumnDefinition _scrollSliderColumn;

    /// <summary>
    /// TODO
    /// </summary>
    Control _scrollViewerFocusControl;

    /// <summary>
    /// TODO
    /// </summary>
	ScrollViewer _scrollViewer;

    /// <summary>
    /// TODO
    /// </summary>
    ContentPresenter _extentParent;

    /// <summary>
    /// TODO
    /// </summary>
    ButtonBase _lineUpButton;

    /// <summary>
    /// TODO
    /// </summary>
    ButtonBase _pageUpButton;

    /// <summary>
    /// TODO
    /// </summary>
    ButtonBase _pageDownButton;

    /// <summary>
    /// TODO
    /// </summary>
    ButtonBase _lineDownButton;

    /// <summary>
    /// TODO
    /// </summary>
    TouchSlider _touchSlider;

    /// <summary>
    /// The width of the <n>Grid</n> column named <r>ScrollButtonColumnName</r> as specified in the template.
    /// </summary>
    GridLength _scrollButtonColumnOriginalWidth;

    /// <summary>
    /// The width of the <n>Grid</n> column named <r>ScrollSliderColumnName</r> as specified in the template.
    /// </summary>
    GridLength _scrollSliderColumnOriginalWidth;

    /// <summary>
    /// The margin inside of <r>_scrollViewer</r> that is occupied by the focus visual element.
    /// In other words, the actual viewable area of the viewport is effectively reduced by this
    /// amount (on all four sides) when this control has focus.
    /// </summary>
	double _scrollMargin;

    /// <summary>
    /// Line up/down is this fraction of page up/down.
    /// </summary>
    const double _lineUpFraction = 0.1;

    bool _isDragging;
    Point _dragStartPoint;
    double _dragStartOffset;

    /// <summary>
    /// Gets the extent, i.e. the element which is scrolled.
    /// </summary>
    protected FrameworkElement Extent
    {
        get
        {
            return (FrameworkElement) _extentParent.Content;
        }
    }

    protected ScrollViewer ScrollViewer
    {
        get
        {
            return _scrollViewer;
        }
    }

    public UiVisibility ScrollButtonVisibility
    {
        get
        {
            return (UiVisibility)GetValue(ScrollButtonVisibilityProperty);
        }
        set
        {
            SetValue(ScrollButtonVisibilityProperty, value);
        }
    }

    public UiVisibility ScrollSliderVisibility
    {
        get
        {
            return (UiVisibility)GetValue(ScrollSliderVisibilityProperty);
        }
        set
        {
            SetValue(ScrollSliderVisibilityProperty, value);
        }
    }

    public bool IsWaiting
    {
        get
        {
            return (bool)GetValue(IsWaitingProperty);
        }
        set
        {
            SetValue(IsWaitingProperty, value);
        }
    }

    public Visibility PageButtonVisibility
    {
        get
        {
            return (Visibility)GetValue(PageButtonVisibilityProperty);
        }
        set
        {
            SetValue(PageButtonVisibilityProperty, value);
        }
    }

    public double ViewportMaxWidth
    {
        get
        {
            return (double)GetValue(ViewportMaxWidthProperty);
        }
        set
        {
            SetValue(ViewportMaxWidthProperty, value);
        }
    }

    public double ViewportMaxHeight
    {
        get
        {
            return (double)GetValue(ViewportMaxHeightProperty);
        }
        set
        {
            SetValue(ViewportMaxHeightProperty, value);
        }
    }

    /// <summary>
    /// Gets or sets a value which indicates if display of overall focus to the control is
    /// enabled.
    /// </summary>
    public bool SuppressFocusDisplay
    {
        get
        {
            return (bool)GetValue(SuppressFocusDisplayProperty);
        }
        set
        {
            SetValue(SuppressFocusDisplayProperty, value);
        }
    }

    public TouchScrollViewer() 
    {
        DefaultStyleKey = typeof(TouchScrollViewer);
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        ControlHelper.GetRequiredTemplateChild(this, LayoutRootName, out _layoutRoot);
        ControlHelper.GetRequiredTemplateChild(this, ScrollButtonColumnName, out _scrollButtonColumn);
        ControlHelper.GetRequiredTemplateChild(this, ScrollSliderColumnName, out _scrollSliderColumn);
        ControlHelper.GetRequiredTemplateChild(this, ScrollViewerFocusControlName,
			out _scrollViewerFocusControl);
		ControlHelper.GetRequiredTemplateChild(this, ScrollViewerName, out _scrollViewer);
		ControlHelper.GetRequiredTemplateChild(this, ExtentParentName, out _extentParent);
		ControlHelper.GetRequiredTemplateChild(this, LineUpButtonName, out _lineUpButton);
		ControlHelper.GetRequiredTemplateChild(this, PageUpButtonName, out _pageUpButton);
		ControlHelper.GetRequiredTemplateChild(this, PageDownButtonName, out _pageDownButton);
		ControlHelper.GetRequiredTemplateChild(this, LineDownButtonName, out _lineDownButton);
		ControlHelper.GetRequiredTemplateChild(this, TouchSliderName, out _touchSlider);

        UiUtil.GetRequiredResource(_layoutRoot, "ScrollMargin", out _scrollMargin);

        _layoutRoot.MouseLeftButtonDown += (sender, e) => OnLayoutRootLeftButtonDown(e);

        _scrollViewerFocusControl.GotFocus += (sender, e) => OnGotFocus(e);
        _scrollViewerFocusControl.LostFocus += (sender, e) => OnLostFocus(e);
        _scrollViewerFocusControl.KeyDown += (sender, e) => OnKeyDown(e);
        _scrollViewerFocusControl.KeyUp += (sender, e) => OnKeyUp(e);

        _extentParent.SizeChanged += (sender, e) => OnExtentParentSizeChanged(e);
        _extentParent.MouseLeftButtonDown += (sender, e) => OnExtentParentMouseLeftButtonDown(e);
        _extentParent.MouseMove += (sender, e) => OnExtentParentMouseMove(e);
        _extentParent.MouseLeftButtonUp += (sender, e) => OnExtentParentMouseLeftButtonUp(e);

        _lineUpButton.Click += (sender, e) => OnScrollButtonClick(Key.Up);
        _pageUpButton.Click += (sender, e) => OnScrollButtonClick(Key.PageUp);
        _pageDownButton.Click += (sender, e) => OnScrollButtonClick(Key.PageDown);
        _lineDownButton.Click += (sender, e) => OnScrollButtonClick(Key.Down);

        _touchSlider.ValueChanged += (sender, e) => OnSliderValueChanged(e);

        _scrollButtonColumnOriginalWidth = _scrollButtonColumn.Width;
        _scrollSliderColumnOriginalWidth = _scrollSliderColumn.Width;

        // initialize the visual state of the control
        UpdateVisualState(false);
    }

    protected override void OnGotFocus(RoutedEventArgs e)
    {
        base.OnGotFocus(e);
        if (!ClientGlobals.IsTouchMode && !SuppressFocusDisplay)
            VisualStateManager.GoToState(this, FocusedName, true);
    }

    protected override void OnLostFocus(RoutedEventArgs e)
    {
        base.OnLostFocus(e);
        //if (!ControlHelper.IsTouchMode)
		VisualStateManager.GoToState(this, UnfocusedName, true);
    }

    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);
        if (PerformKeyboardNavigation(e.Key))
            e.Handled = true;
    }

    protected virtual void OnExtentParentSizeChanged(SizeChangedEventArgs e)
    {
        double scrollRange = _scrollViewer.ExtentHeight - _scrollViewer.ViewportHeight;
        if (scrollRange > 0)
        {
            _touchSlider.Maximum = _scrollViewer.ExtentHeight - _scrollViewer.ViewportHeight;
            _touchSlider.ThumbSize = _scrollViewer.ViewportHeight / _scrollViewer.ExtentHeight;
            EnableScrolling(true, true);
        }
        else
            EnableScrolling(false, true);
    }

    protected virtual void OnLayoutRootLeftButtonDown(MouseButtonEventArgs e)
    {
        _scrollViewerFocusControl.Focus();
    }

    protected virtual void OnExtentParentMouseLeftButtonDown(MouseButtonEventArgs  e)
    {
        _isDragging = true;
        _dragStartPoint = e.GetPosition(_scrollViewer);
        _dragStartOffset = _scrollViewer.VerticalOffset;
        _extentParent.CaptureMouse();
    }

    protected virtual void OnExtentParentMouseMove(MouseEventArgs  e)
    {
        if (_isDragging && _scrollingUiIsEnabled)
        {
            Point point = e.GetPosition(_scrollViewer);
            double offset = _dragStartOffset + _dragStartPoint.Y - point.Y;
            ScrollToOffset(offset, true, true);
        }
    }

    protected void ScrollToOffset(double offset, bool scrollExtent, bool scrollSlider)
    {
        if (scrollExtent)
            _scrollViewer.ScrollToVerticalOffset(offset);
        if (scrollSlider)
            _touchSlider.Value = offset;
    }

    protected virtual void OnExtentParentMouseLeftButtonUp(MouseButtonEventArgs  e)
    {
        _isDragging = false;
        _extentParent.ReleaseMouseCapture();
    }

    protected virtual void OnScrollButtonClick(Key key)
    {
        PerformScrollButtonNavigation(key);
    }

    protected virtual void OnSliderValueChanged(RoutedPropertyChangedEventArgs<double> e)
    {
        ScrollToOffset(_touchSlider.Value, true, false);
    }

    static void OnScrollButtonVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchScrollViewer)d;
        if (target._layoutRoot != null) // i.e. if OnApplyTemplate was called
            target.EnableScrolling(target._scrollingUiIsEnabled, true);
    }

    static void OnScrollSliderVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchScrollViewer)d;
        if (target._layoutRoot != null) // i.e. if OnApplyTemplate was called
            target.EnableScrolling(target._scrollingUiIsEnabled, true);
    }

    /// <summary>
    /// <n>true</n> if the UI for scrolling is enabled.
    /// </summary>
    bool _scrollingUiIsEnabled = true;

    /// <summary>
    /// Enables or disables the UI for scrolling.
    /// </summary>
    ///
    /// <param name="enable"><n>true</n> to enable the scrolling UI, <n>false</n> to disable it.
    ///     </param>
    ///     
    /// <param name="force"><n>true</n> to force setting UI state even if <pr>enable</pr> equals
    ///     <r>_scrollingUiIsEnabled</r>.</param>
    ///
    void EnableScrolling(bool enable, bool force)
    {
        if ((_scrollingUiIsEnabled == enable) && !force)
            return;
        _scrollingUiIsEnabled = enable;

        // figure out if the scroll buttons should be collapsed and/or hidden
        bool collapse, hide;
        ClientUtil.InterpretUiVisibility(ScrollButtonVisibility, !enable, out collapse,
            out hide);
        _scrollButtonColumn.Width = collapse ? new GridLength(0) :
            _scrollButtonColumnOriginalWidth;
        Visibility visible = collapse ? Visibility.Collapsed : Visibility.Visible;
        Visibility pageButtonsVisibile =
            ((PageButtonVisibility == Visibility.Visible) ? visible : Visibility.Collapsed);
        _lineUpButton.IsEnabled = enable;
        _lineUpButton.Visibility = visible;
        _pageUpButton.IsEnabled = enable;
        _pageUpButton.Visibility = pageButtonsVisibile;
        _pageDownButton.IsEnabled = enable;
        _pageDownButton.Visibility = pageButtonsVisibile;
        _lineDownButton.IsEnabled = enable;
        _lineDownButton.Visibility = visible;

        // figure out if the scroll slider should be collapsed and/or hidden
        ClientUtil.InterpretUiVisibility(ScrollSliderVisibility, !enable, out collapse,
            out hide);
        _scrollSliderColumn.Width = collapse ? new GridLength(0) :
            _scrollSliderColumnOriginalWidth;
        visible = collapse ? Visibility.Collapsed : Visibility.Visible;
        _touchSlider.IsEnabled = enable;
        _touchSlider.Visibility = visible;
    }

    /// <summary>
    /// Performs scrolling as specified by a given on-screen scroll button.
    /// </summary>
    ///
    /// <param name="key">The keyboard key.</param>
    ///
    /// <returns>
    /// <n>true</n> if the key was handled, <n>false</n> if not.
    /// </returns>
    ///
    protected virtual bool PerformScrollButtonNavigation(Key key)
    {
        return PerformKeyboardNavigation(key);
    }

    /// <summary>
    /// Performs scrolling as specified by a given keyboard key.
    /// </summary>
    ///
    /// <param name="key">The keyboard key.</param>
    ///
    /// <returns>
    /// <n>true</n> if the key was handled, <n>false</n> if not.
    /// </returns>
    ///
    protected virtual bool PerformKeyboardNavigation(Key key)
    {
        switch (key)
        {

        case Key.Up:

            ScrollToOffset(_scrollViewer.VerticalOffset - VisibleViewportHeight * _lineUpFraction, true, true);
            return true;

        case Key.Down:

            ScrollToOffset(_scrollViewer.VerticalOffset + VisibleViewportHeight * _lineUpFraction, true, true);
            return true;

        case Key.PageUp:

            ScrollToOffset(_scrollViewer.VerticalOffset - VisibleViewportHeight, true, true);
            return true;

        case Key.PageDown:

            ScrollToOffset(_scrollViewer.VerticalOffset + VisibleViewportHeight, true, true);
            return true;

        }

        return false;
    }

    /// <summary>
    /// The height of the viewport excluding the part covered up by the focus visual element.
    /// </summary>
    double VisibleViewportHeight
    {
        get
        {
            return _scrollViewer.ViewportHeight - 2 * _scrollMargin;
        }
    }

    static void OnIsWaitingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchScrollViewer)d;
        target.UpdateVisualState(true);
    }

    void UpdateVisualState(bool useTransitions)
    {
        if (IsWaiting)
            VisualStateManager.GoToState(this, WaitingStateName, useTransitions);
        else
            VisualStateManager.GoToState(this, IdleStateName, useTransitions);
    }

	// IControlHelperConnector implementation

    Control IControlHelperConnector.GetControl()
    {
        return this;
    }

    DependencyObject IControlHelperConnector.GetTemplateChild(string childName)
    {
        return GetTemplateChild(childName);
    }
}

}

